satkit 0.16.2

Satellite Toolkit
Documentation
{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": "# Orbital Mean-Element Messages\n\nOrbital Mean-Element Messages (OMMs) are a standardized data product defined by [CCSDS](https://ccsds.org/Pubs/502x0b3e1.pdf) for exchanging satellite orbital elements in a machine-readable way. They contain the same orbital parameters as TLEs but in a more structured format, and are growing in popularity as the modern replacement for TLE distribution.\n\nOMMs are available from [CelesTrak](https://celestrak.org) and [Space-Track](https://www.space-track.org) in multiple encodings:\n\n- **JSON** — the most common and straightforward format\n- **XML** — more verbose with deeper hierarchy, but widely supported\n- **KVN** — key-value notation; imposes very little structure\n\n`satkit` supports SGP4 propagation of OMMs represented as Python dictionaries. KVN is **not** supported. The helper `sk.omm_from_url(url)` fetches an OMM endpoint, auto-detects JSON vs XML, and returns a list of dictionaries that can be passed directly to `sk.sgp4` — no external HTTP or XML libraries needed."
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## Example 1: JSON Format\n",
    "\n",
    "Load a JSON OMM for the International Space Station from CelesTrak, propagate with SGP4, and convert to geodetic coordinates. The JSON format maps directly to a Python dictionary, making it simple to work with."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": "import satkit as sk\n\n# Fetch the current ephemeris for the International Space Station (ISS).\n# sk.omm_from_url auto-detects JSON vs XML response format and returns a list\n# of OMM dictionaries that can be passed directly to sk.sgp4.\nurl = \"https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=json\"\nomm = sk.omm_from_url(url)\n\n# Get a representative time from the OMM epoch\nepoch = sk.time(omm[0][\"EPOCH\"])\n# Create a list of times — once every 10 minutes\ntime_array = [epoch + sk.duration(minutes=i * 10) for i in range(6)]\n\n# TEME (inertial) output from SGP4\npTEME, _vTEME = sk.sgp4(omm[0], time_array)\n\n# Rotate to Earth-fixed\npITRF = [sk.frametransform.qteme2itrf(t) * p for t, p in zip(time_array, pTEME)]\n\n# Geodetic coordinates of space station at given times\ncoord = [sk.itrfcoord(x) for x in pITRF]"
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": "## Example 2: XML Format\n\n`sk.omm_from_url` automatically detects the response format. Passing an XML endpoint returns the same list-of-dicts shape as the JSON version, so the downstream SGP4 call is identical — no `xmltodict` plumbing or manual tree traversal required. Here we fetch the same ISS OMM in XML, propagate it over roughly one orbit, and plot the ground track."
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": "import satkit as sk\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport scienceplots  # noqa: F401\nplt.style.use([\"science\", \"no-latex\", \"../satkit.mplstyle\"])\n%config InlineBackend.figure_formats = ['svg']\nimport warnings\nwarnings.filterwarnings(\"ignore\", \"Downloading\")\nimport cartopy.crs as ccrs\nimport cartopy.feature as cfeature\n\n# Fetch the current ISS ephemeris in XML format.  sk.omm_from_url auto-detects\n# the format and normalizes the result to the same list-of-dicts shape as the\n# JSON example above — no xmltodict or hand-written tree walking needed.\nurl = \"https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=xml\"\nomm = sk.omm_from_url(url)[0]\n\n# Get a representative time from the OMM epoch\nepoch = sk.time(omm[\"EPOCH\"])\n# Time array spanning roughly one orbit at 1-minute cadence\ntime_array = [epoch + sk.duration(minutes=i) for i in range(97)]\n\n# TEME (inertial) output from SGP4\npTEME, _vTEME = sk.sgp4(omm, time_array)\n\n# Rotate to Earth-fixed\npITRF = [sk.frametransform.qteme2itrf(t) * p for t, p in zip(time_array, pTEME)]\n\ncoord = [sk.itrfcoord(x) for x in pITRF]\n\nlat, lon, alt = zip(*[(c.latitude_deg, c.longitude_deg, c.altitude) for c in coord])\n\n# Break ground track at longitude discontinuities (date line crossings)\nlon_arr, lat_arr = np.array(lon), np.array(lat)\nbreaks = np.where(np.abs(np.diff(lon_arr)) > 180)[0] + 1\nlon_segs = np.split(lon_arr, breaks)\nlat_segs = np.split(lat_arr, breaks)\n\nfig, ax = plt.subplots(figsize=(10, 5), subplot_kw={\"projection\": ccrs.PlateCarree()})\nax.add_feature(cfeature.LAND, facecolor=\"lightgray\")\nax.add_feature(cfeature.BORDERS, linewidth=0.5)\nax.add_feature(cfeature.COASTLINE, linewidth=0.5)\nfor lo, la in zip(lon_segs, lat_segs):\n    ax.plot(lo, la, linewidth=1.5, color=\"C0\", transform=ccrs.PlateCarree())\nax.set_title(\"ISS Ground Track\")\nax.set_global()\nplt.tight_layout()\nplt.show()\n\nfig, ax = plt.subplots(figsize=(10, 4))\nax.plot([t.datetime() for t in time_array], np.array(alt) / 1e3)\nax.set_xlabel(\"Time\")\nax.set_ylabel(\"Altitude (km)\")\nax.set_title(\"ISS Altitude vs Time\")\nfig.autofmt_xdate()\nplt.tight_layout()\nplt.show()"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.14.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}